#! /usr/bin/env python

import os
############################ your personal settings ############################

browser = 'firefox'
editor = "nano"

base = "arbeitszeit"				 # basename of the log file
weekly_worktime = 38.5				 # Working hours per week
workspace		= 0.5 				 # hoursworking break per day

last_line = 'last | grep -e "'+os.environ['USER']+' \{0,\}:0"'  # What is to grep from last ?

folder = os.environ['HOME']+"/arbeitszeit/" # where to store the data
exception_file = folder+'ausnahmen' # Datei fuer Urlaubstage, Krankheit, Seminare

leave_days = 26 + 6 # Zahl der Urlaubstage

working_week = [0,1,2,3,4] 	# Tage an denen gearbeitet wird 0  Montag, 6- Sonntag
freetime_week = [5,6]		# Tage an denen nicht gearbeitet wird


############################## Nothing more to to ##############################

import sys
import os.path
import sys
import calendar
import time
import re
import datetime

BLACK = '\033[1;1m'
KRANK = '\033[1;30m'
RED = '\033[1;31m'
GREEN = '\033[1;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[1;34m'
PINK = '\033[1;35m'

class working_time:
	def __init__(self, ident, time=0):
		self.ident = ident
		self.time = time
		self.soll = 0.

class DayDesc:
	"""Describes exceptional days as leave days, illnesses and 'out of building work'"""
	def __init__(self, nr, infostring="", timediff=0):
		self.day_nr = int(nr)
		self.info = infostring
		self.timediff = timediff
	def day(self):
		return self.day_nr

def in_holidays(day, liste):
	day = int(day)
	for d in liste:
		if day == d.day(): return True
	return False

def get_DayDesc(day, liste):
	day = int(day)
	for d in liste:
		if day == d.day(): return d
	return None

def get_color_code(dayobj):
	if   dayobj.info[0] == 'W': return BLUE
	elif dayobj.info[0] == 'A': return PINK
	elif dayobj.info[0] == 'U': return RED
	elif dayobj.info[0] == 'H': return GREEN
	elif dayobj.info[0] == 'K': return KRANK
	else:	return YELLOW


# create target path if needed
if not os.path.exists(folder):
	try:os.makedirs(folder)
	except: pass

#do not edit
now = time.ctime().split()
act_year = now[4]
hostname = os.popen('hostname').readlines()[0].strip()

#print now
#cmd = 'last | grep "%s"'%(last_line)
cmd = last_line
file = os.path.join(folder, base+'_'+act_year+'_'+hostname+'.log')
weekly_worktime_in_min = weekly_worktime * 60
daily_worktime_in_min = abs( float(weekly_worktime_in_min)/ len(working_week) + (workspace * 60))

def color(string, count=1, code='\033[1;1m'):
	return code+string+'\033[0m'

def printHTMLCalendar(month, days_at_work):
	monthMatrix = calendar.monthcalendar(int(act_year), month)
	r = ''
	# print out the heading
	r+= "\n<br>Anwesenheit im "
	r+= "%s %d\n" % (calendar.month_name[month], int(act_year))
	# print out each day in hex; if the day = 0, then leave it blank
	r += '<table>\n'
	for week in monthMatrix:
		r+= '<tr>'
		for day in week:
			if   day == 0:	r+= "&nbsp;&nbsp;"
			elif day in days_at_work[month]:
				r+= "<td><b>%s</b></td>" %(day) # print in bold
			else:
				r+= "<td>%2d</td>" % (day)

		r+= '</tr>\n'
	r += "</table>"
	return r

def printCalendar(month, days_at_work, days_in_holiday):
	monthMatrix = calendar.monthcalendar(int(act_year), month)

	# print out the heading
	print "\nAnwesenheit im ",
	print "%s %d\n" % (calendar.month_name[month], int(act_year))
	# print out each day in hex; if the day = 0, then leave it blank

	for week in monthMatrix:
		for day in week:
			if   day == 0:	print "  ",
			elif day in days_at_work[month]:
				print "%s" %(color("%2d" % (day))), # print in bold
			elif in_holidays(day, days_in_holiday[month]):
				desc = get_DayDesc(day, days_in_holiday[month])
				colorcode = get_color_code(desc)
				print "%s" %(color("%2d" % (day), code=colorcode)), # print in bold
#				print "%s" %(color("%2d" % (day), code=RED)), # print in bold

			else:
				print "%2d" % (day),

		print

def next_valid_date(year, month, day, deltadays=1):
	"""Adds deltadays to a date given by year, month and day and returns
	datetime.date object"""
	year  = int(year)
	month = int(month)
	day   = int(day)

	dateobj = datetime.date(year, month, day)
	delta = datetime.timedelta(deltadays)

	return dateobj + delta

def prev_valid_date(year, month, day, deltadays=1):
	"""Subtracts deltadays to a date given by year, month and day and returns
	datetime.date object"""
	return next_valid_date(year, month, day, -1 * deltadays)

def read_exception_file():
	f = open(exception_file, "r");
	lines = f.readlines()
	f.close()

	days_in_holiday = []
	for i in range(13):	days_in_holiday.append([])

	for i in lines:
		if i.strip().startswith('#'): continue # ignore comments
		if len(i.strip()) == 0: continue # ignore comments
		description = i[0]
#		if i[0] != 'U' : continue

#		date = i[2:12].split('.')

#		if i[13] == '-' : date_end = i[15:25].split('.')

		date 		= None
		date_end	= None
		res = re.findall('([0-9]{1,2}).([0-9]{1,2}).([0-9]{2,4})', i)
#		print len(res), res

		if len(res) > 0: date = res[0]
		if len(res) > 1: date_end = res[1]
		if len(res) > 2:
			print('Warning : Additional date found !')
			print res

		res = re.findall('([0-9]{1,3}:[0-9]{1,2})', i)
		tdiff_min = 0
		if len(res) > 0 :
			tdiff = res[0]
			tdiff = tdiff.split(':')
			tdiff_min = int(tdiff[0]) * 60 + int(tdiff[1])
#			print tdiff_min

		# add all days between start and end to holidays
		if date_end:
			startdate = datetime.date(int(date[2]), int(date[1]), int(date[0]))
			enddate   = datetime.date(int(date_end[2]), int(date_end[1]), int(date_end[0]))

			iter = startdate
			while iter != enddate:
				iter = next_valid_date(iter.year, iter.month, iter.day)
				try:
					days_in_holiday[iter.month].append(DayDesc(iter.day, description, timediff = tdiff_min))

				except:
					pass

		try:
			days_in_holiday[int(date[1])].append(DayDesc(date[0], description, timediff = tdiff_min))
#			days_in_holiday[int(date[1])].append(DayDesc(date[0], description))
		except:
			pass

	return days_in_holiday


def statistics(list):
	"""Make some statistics"""
	tnow = os.popen('date +"%k:%M:%S"').readlines()[0].strip().split(':')

	months = []
	days = []
	for i in range(13) : months.append(calendar.month_abbr[i])
	for i in range(7)  : days.append(calendar.day_abbr[i])

	month = months.index(now[1])

	startdate  = datetime.date(int(now[4]), 1, 1)
	today_date = datetime.date(int(now[4]), month, int(now[2]))

	result =[]
	while startdate != today_date:
		startdate = next_valid_date(startdate.year, startdate.month, startdate.day)
		if startdate.weekday() == 0: # for every monday append an item

			ident = "Mon %s %d" % (months[startdate.month], startdate.day)
			result.append(working_time(ident, 0))
			result[-1].soll = daily_worktime_in_min * len(working_week)

#	# prepare list for every month which contains the days at work
	days_at_work = []
	for i in range(13):	days_at_work.append([])

	for i in list:
		Tag   = re.findall('([a-zA-Z]{3}\ {0,3}[a-zA-Z]{3}\ {0,3}[0-9]{1,2})', i)[0].split()
		dauer = re.findall('([0-9]{2}:[0-9]{2})\)', i)[0].split(':')

		prev_monday = prev_valid_date(act_year, months.index(Tag[1]), Tag[2], days.index(Tag[0]))
		last_monday_found = "Mon %s %d" % ( months[prev_monday.month], prev_monday.day)

		for j in range(len(result)):
			if last_monday_found == result[j].ident:
				result[j].time += int(dauer[0]) * 60 + int(dauer[1])
				days_at_work[months.index(Tag[1])].append(int(Tag[2])) # Arbeitstage speichern
				break

	try:
		this_login = os.popen(cmd + '| grep "still logged"').readlines()[0]
		start_today = re.findall('.*([0-9]{2}:[0-9]{2}).*', this_login)[0].split(':')
		days_at_work[month].append(int(now[2])) # Arbeitstage speichern
	except:
		this_login = None

	if '--html' in sys.argv:
		s = ''

		if this_login:#print now
			start_today_in_s = int(start_today[0]) * 60 * 60 + int(start_today[1]) * 60
			now_in_s		 = int(tnow[0]) * 60 * 60 + int(tnow[1]) * 60

			diff_in_s = now_in_s - start_today_in_s
			diff_in_min = int(diff_in_s / 60.)

		#print time.fmtdate("%d", time.now())
		s+= "\nWochenarbeitsstunden:<br>"
		s += '<table border = 1 width = 500 ><tr>'
		s+= "<td>%s</td><td>%s</td>" % ("Woche vom".ljust(12), "Arbeitszeit")
		s += '</tr>\n'
		for i in result:
			s+='<tr>'
			s+= "<td>%s</td><td>%02d:%02d</td>" %(i.ident.ljust(12), int(i.time/60), i.time%60)
			s += '</tr>\n'
		s+='<tr>'
		if this_login:
			s+= "<td>%s</td><td>%02d:%02d</td>" %("heute".ljust(12), diff_in_min/60, diff_in_min%60)
			s+='</tr>\n'
		s+='</table>'

		if '-y' in sys.argv:
			for i in range (1,13):
				s += printHTMLCalendar(i, days_at_work)
		else:
			s +=printHTMLCalendar(month, days_at_work)
		html = os.path.join(folder,'anwesenheit.html')
		f = open(html, 'w')
		f.write(s)
		f.close()

		print "Output written to ", html, '.'
		os.system(browser + " " + html + '&')

	else:
		exceptional_days = read_exception_file()

		################### correct the weekly worktime ########################
		for a_month in range(len(exceptional_days)):
			for day in exceptional_days[a_month]:
				edate = datetime.date(int(act_year), a_month, day.day())
				date = prev_valid_date(act_year, a_month, day.day(), edate.weekday())
				ident = "Mon %s %d" % (months[date.month], date.day)

				for j in range(len(result)):
					if ident == result[j].ident:
						if edate.weekday() in working_week and day.info in "AHKU" : # bei Krankheit, Urlaub, Feiertag, Abgeltungstag in der Woche das Soll verringern
							result[j].soll -= int(daily_worktime_in_min)
						if day.info == "W" : # bei Arbeit ausser Haus geleistete Zeit erhoehen
#							print day.timediff
							if day.timediff > 0: # working days are expected to be complete, but if a time was provided then use this one
								result[j].time += day.timediff
							else:
								result[j].time += int(daily_worktime_in_min)
						break
		########################################################################

		if '-y' in sys.argv:
			for i in range (1,13):
				printCalendar(i, days_at_work, exceptional_days)
		else:
			print "aktueller Monat", month
			printCalendar(month, days_at_work, exceptional_days)

		if this_login:#print now
			start_today_in_s = int(start_today[0]) * 60 * 60 + int(start_today[1]) * 60
			now_in_s		 = int(tnow[0]) * 60 * 60 + int(tnow[1]) * 60

			diff_in_s = now_in_s - start_today_in_s
			diff_in_min = int(diff_in_s / 60.)

		#print time.fmtdate("%d", time.now())

		print "\nUrlaubstage \t\t\t:", leave_days
		holiday_count = 0
		for month in range(13):
			for d in exceptional_days[month]:
				date = datetime.date(int(act_year), month, d.day())

				if d.info == 'U' and date.weekday() in working_week:
					holiday_count += 1

		print "davon verbraucht \t\t:", holiday_count

		print "an Wochenenden gearbeitet \t:",
		work_count = 0
		for month in range(13):
			for d in days_at_work[month]:
				date = datetime.date(int(act_year), month, d)
				if date.weekday() in freetime_week:
					work_count += 1
			for d in exceptional_days[month]:
				date = datetime.date(int(act_year), month, d.day())
				if d.info == 'W' and date.weekday() in freetime_week:
					work_count += 1
		print work_count

		print "\nWochenarbeitsstunden:"
		print "%s| %s" % ("Woche vom".ljust(12), "Arbeitszeit Differenz")
		print 29*'-'

		# list all weeks with working hours
#		soll = int(weekly_worktime_in_min) + int(workspace*60 * len(working_week))
		sum_soll = 0
		sum_ist  = 0
		for i in result:
			print "\n%s| %02d:%02d" %(i.ident.ljust(12), int(i.time/60), i.time%60),
			sign= '-'
			# Restzeit
			if i.time > i.soll:
				colorcode = RED
				sign= '+'
			else:
				colorcode = BLUE
				sign= '-'
			d = abs(i.soll - i.time)
#			print i.soll
			print color(" %s%02d:%02d ( Soll : %02d:%02d )" % (sign, d/60, d%60, i.soll/60, i.soll%60),code=colorcode),

			if i.ident != result[-1].ident: # aktuelle Woche nicht rechnen
				sum_soll += i.soll
				sum_ist  += i.time
		print

		# list working hours today (since last login)
		if this_login:
			print "%s| %02d:%02d" %("heute".ljust(12), diff_in_min/60, diff_in_min%60),
			sign= '-'
			# Restzeit heute
			if diff_in_min > weekly_worktime_in_min/ len(working_week):
				colorcode = RED
				sign = '+'
			else:
				colorcode = BLUE
				sign = '-'
			soll = abs(weekly_worktime_in_min/ len(working_week) + workspace*60)
			d = soll - diff_in_min
			print color(" %s%02d:%02d ( Soll : %02d:%02d )" % (sign, d/60, d%60, soll/60, soll%60 ),code=colorcode),
#			sum_soll += soll
#			sum_ist  += diff_in_min

		print "\nUeberhang           ",
		diff = sum_soll - sum_ist
		colorcode = BLUE
		sign = '-'
		if diff < 0 :
			colorcode = RED
			sign = '+'
			diff = abs(diff)
#		print "%02d:%02d"% (sum_ist/60, sum_ist%60),
		print  color("%s%02d:%02d" % (sign, diff/60, diff%60), code=colorcode),
		print "( ohne laufende Woche )"


if '-?' in sys.argv:
	print """Dieses Skript erfasst die Arbeitszeit mit dem Befehl
 $ %s

Die Daten werden in der Datei
 %s
abgelegt.


Anzeige der Daten mit
 $ arbeitszeit.py -l.

Berechnen der Wochenstunden und Anwesenheiten mit
 $ arbeitszeit.py -s.

Die Option --silent verhindert Ausgaben wenn das Script ohne Parameter
aufgerufen wird.

Ausnahmenbehandlung:
 Kranheitstage und Tage, an denen man anderweitiggearbeitet hat und nicht
 am Rechner sass, koennen gesondert erfasst werden. Dies geschieht in der
 Datei %s. Diese hat das Format

 Kuerzel Datum - Datum Kommentar

 Das Kuerzel kann sein:
  W: Work - ich habe gearbeitet aber woanders
  U: Urlaub - ich habe richtigen Urlaub genommen
  H: Holiday - gesetzlicher Feiertag
  K: Krankheitstag
  A: Abgeltungstag
  S: sonstiges


 Weitere Optionen:
 --html: Schreibt die Ausgabe als HTML-Datei (nur mit -s).
 -y    : gibt einen kompletten Jahreskalender aus (nur mit -s oder -html)
 -U    : zeigt die Liste der Ausnahmen an (Urlaub, Tagungen, whatever ... )
 -e    : oeffnet %s um die Ausnahmendatei zu editieren

""" %(cmd, file, exception_file, editor)
	sys.exit(0)


if '-l' in sys.argv: # show the logfile
	os.system('cat '+file)
	sys.exit(0)

if '-U' in sys.argv: # show the exception file
	os.system('cat '+exception_file)
	sys.exit(0)

if '-e' in sys.argv: # show the exception file
	os.system(editor +' '+exception_file)
	sys.exit(0)

silent = '--silent' in sys.argv
if '--html' in sys.argv and not '-s' in sys.argv:
	sys.argv.append('-s')

if not silent: print "Erfasse die Arbeitszeit mit dem Befehl \n$ %s" %(cmd)

lastlog = os.popen(cmd).readlines()

lastlog.reverse()

if os.path.exists(file):
	f = open(file, "r")
	lines = f.readlines()
	f.close()
else: lines = []

# append data
f = open(file, "a")
for i in lastlog:
	if not i in lines and not "still logged in" in i:
		if not silent: print i.strip()
		f.write(i)
		lines.append(i)
f.close()

if '-s' in sys.argv: # show the logfile
	statistics(lines)

